package com.lier.gulimall.product.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.TypeReference;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.lier.common.vo.Catalog2VO;
import com.lier.gulimall.product.dao.CategoryBrandRelationDao;
import com.lier.gulimall.product.entity.CategoryBrandRelationEntity;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.Caching;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.lier.common.utils.PageUtils;
import com.lier.common.utils.Query;

import com.lier.gulimall.product.dao.CategoryDao;
import com.lier.gulimall.product.entity.CategoryEntity;
import com.lier.gulimall.product.service.CategoryService;
import org.springframework.util.StringUtils;

import javax.annotation.Resource;


@Service("categoryService")
public class CategoryServiceImpl extends ServiceImpl<CategoryDao, CategoryEntity> implements CategoryService {

    @Resource
    private CategoryBrandRelationDao categoryBrandRelationDao;
    @Resource
    private CategoryService categoryService;
    @Resource
    private StringRedisTemplate stringRedisTemplate;
    @Resource
    private RedissonClient redissonClient;

    @Override
    public PageUtils queryPage(Map<String, Object> params) {
        IPage<CategoryEntity> page = this.page(
                new Query<CategoryEntity>().getPage(params),
                new QueryWrapper<>()
        );

        return new PageUtils(page);
    }

    @Override
    public List<CategoryEntity> listTree() {

        // 查出所有分类数据
        List<CategoryEntity> categoryEntityList = baseMapper.selectList(null);

        return categoryEntityList.stream()
                .filter(item -> item.getParentCid() == 0)
                // 设置当前菜单的子菜单
                .peek(menu -> menu.setChildren(getCurrentMenuChildren(menu, categoryEntityList)))
                // 排序
                .sorted(Comparator.comparingInt(CategoryEntity::getSort))
                .collect(Collectors.toList());
    }

    @Override
    public void deleteBatchByIds(List<Long> list) {

        // todo 检测删除的id是否又地方引用, 没有再删除

        // 逻辑删除
        baseMapper.deleteBatchIds(list);
    }

    @Override
    public List<Long> findCatelogPath(Long catelogId) {
        List<Long> paths = new ArrayList<>();
        List<Long> resPath = getParentPath(catelogId, paths);
        Collections.reverse(resPath);
        return resPath;
    }

//    @CacheEvict(value = {"category"}, key = "'getLevel1Categorys'")
    // 清除category分区的所有缓存  --> 方式一
//    @Caching(evict = {
//            @CacheEvict(value = {"category"}, key = "'getLevel1Categorys'"),
//            @CacheEvict(value = {"category"}, key = "'getCatalogData'")
//    })
//    清除category分区的所有缓存  --> 方式二
    @CacheEvict(value = {"category"}, allEntries = true)
    @Override
    public void updateDetail(CategoryEntity category) {
        baseMapper.updateById(category);

        if(category.getName() != null){
            LambdaUpdateWrapper<CategoryBrandRelationEntity> updateWrapper = new LambdaUpdateWrapper<>();
            updateWrapper.set(CategoryBrandRelationEntity::getCatelogName, category.getName())
                    .eq(CategoryBrandRelationEntity::getCatelogId, category.getCatId());

            categoryBrandRelationDao.update(null, updateWrapper);
        }
    }

    /**
     * 查出所有1级分类
     */
    @Cacheable(value = {"category"}, key = "'getLevel1Categorys'", sync = true)
    @Override
    public List<CategoryEntity> getLevel1Categorys() {
        return baseMapper.selectList(new LambdaQueryWrapper<CategoryEntity>().eq(CategoryEntity::getParentCid, 0));
    }

    @Cacheable(value = {"category"}, key = "#root.methodName", sync = true)
    @Override
    public Map<String, List<Catalog2VO>> getCatalogData(){

        // 获取一级分类
        List<CategoryEntity> level1Categorys = categoryService.getLevel1Categorys();


        Map<String, List<Catalog2VO>> result = level1Categorys.stream()
                .collect(Collectors.toMap(k -> String.valueOf(k.getCatId()), v -> {
                    List<CategoryEntity> categoryEntityList = baseMapper.selectList(new LambdaQueryWrapper<CategoryEntity>()
                            .eq(CategoryEntity::getParentCid, v.getCatId()));

                    // 组装二级分类
                    List<Catalog2VO> catalog2VOList = new ArrayList<>();
                    if (categoryEntityList != null && !categoryEntityList.isEmpty()) {
                        catalog2VOList = categoryEntityList.stream().map(item -> {

                            // 组装三级分类
                            List<CategoryEntity> categoryEntities = baseMapper.selectList(new LambdaQueryWrapper<CategoryEntity>().eq(CategoryEntity::getParentCid, item.getCatId()));
                            List<Catalog2VO.Catalog3Vo> catalog3VoList = new ArrayList<>();
                            if (categoryEntities != null && !categoryEntities.isEmpty()) {
                                catalog3VoList = categoryEntities.stream()
                                        .map(level3 -> new Catalog2VO.Catalog3Vo(item.getCatId().toString(), level3.getCatId().toString(), level3.getName())).collect(Collectors.toList());
                            }
                            return new Catalog2VO(v.getCatId().toString(), catalog3VoList, item.getCatId().toString(), item.getName());
                        }).collect(Collectors.toList());
                    }

                    return catalog2VOList;
                }));

        return result;
    }

    public Map<String, List<Catalog2VO>> getCatalogData1() {


        String categoryJson = stringRedisTemplate.opsForValue().get("categoryJson");

        if(StringUtils.isEmpty(categoryJson)){
            System.out.println("未命中缓存, 查询数据库");
//            return getCatalogWithLocalLock();
//            return getCatalogWithRedisLock();
            return getCatalogWithRedissonLock();
        }

        System.out.println("命中缓存, 直接返回");
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            log.error(e.getMessage());
        }
        return JSON.parseObject(categoryJson, new TypeReference<Map<String, List<Catalog2VO>>>(){});
    }


    private Map<String, List<Catalog2VO>> getCatalogWithRedissonLock(){

        // 占分布式锁
        RLock lock = redissonClient.getLock("redisson-lock");
        lock.lock();
        Map<String, List<Catalog2VO>> catalogFromDb = null;
        try{
            // 加锁成功, 从数据库读, 业务代码
            catalogFromDb = getCatalogFromDb();
        }finally {
            lock.unlock();
        }

        return catalogFromDb;
    }

    /**
     * 使用Redis实现分布式锁
     * @return 数据
     */
    private Map<String, List<Catalog2VO>> getCatalogWithRedisLock(){

        // 占分布式锁, 并设置过期时间, 避免死锁
        String token = UUID.randomUUID().toString();
        // 加锁时, 加锁和设置过期时间必须一起设置保证原子性, 避免在加锁后还没设置过期时间前, 服务器宕机导致未设置过期时, 且在删除锁之前
        // 某些原因导致删除锁失败, 又没设置锁过期时间导致死锁
        Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent("lock", token, 5, TimeUnit.MINUTES);
        if(Boolean.TRUE.equals(lock)){

            System.out.println("获取分布式锁成功");
            Map<String, List<Catalog2VO>> catalogFromDb = null;
            try{
                // 加锁成功, 从数据库读, 业务代码
                catalogFromDb = getCatalogFromDb();
            }finally {

                /**
                 * 执行完成, 删除锁
                 * 删除锁时, 必须判断时当前线程自己的锁, 否则可能会删除其他锁, 因为如果业务代码执行时间过长, 锁设置过期时间过期了, 其他业务还会进来
                 * 这时候如果当前业务执行完成, 不判是是否当前线程的锁, 则会将其他线程的锁一并删除
                 */

                // 这种操作并非原子操作, 如果在获取锁的值从redis返回时, 锁刚好过期, 获取的是之前锁的值, 是正确的
                // 但是锁刚好就过期了, 这时别的线程又刚好设置了锁, 那这里删除就会删除到别的线程的锁
                // 需保证获取和删除是原子操作
    //            String existLockValue = stringRedisTemplate.opsForValue().get("lock");
    //            if(token.equals(existLockValue)){
    //                // 删除自己的锁
    //                stringRedisTemplate.delete("lock");
    //            }

                // 使用lua脚本实现原子操作获取和删除锁
                String luaScript = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
                // 删除锁
                Long luaLock = stringRedisTemplate
                        .execute(new DefaultRedisScript<>(luaScript, Long.class),
                                Collections.singletonList("lock"), token);
            }

            return catalogFromDb;
        }else {
            // 加锁失败..., 重试, 自旋
            System.out.println("获取分布式锁失败, 重试");
            return getCatalogWithLocalLock();
        }
    }


    /**
     * 本地锁: 只能锁住当前服务, 无法锁住分布式服务中其他服务
     * @return 数据
     */
    private Map<String, List<Catalog2VO>> getCatalogWithLocalLock(){
        synchronized (this) {
            System.out.println("获取本地锁");
            return getCatalogFromDb();
        }
    }

    /**
     * 从数据库获取分类数据
     * @return 分类数据
     */
    private Map<String, List<Catalog2VO>> getCatalogFromDb() {

        // 获取锁的线程先判断缓存中是否有数据, 有则不必再从数据库获取, 避免大并发请求同一个key, 压垮数据库
        String categoryJson = stringRedisTemplate.opsForValue().get("categoryJson");
        if(!StringUtils.isEmpty(categoryJson)){
            return JSON.parseObject(categoryJson, new TypeReference<Map<String, List<Catalog2VO>>>(){});
        }

        // 缓存中没有数据
        // 获取一级分类
        List<CategoryEntity> level1Categorys = categoryService.getLevel1Categorys();


        Map<String, List<Catalog2VO>> result = level1Categorys.stream()
                .collect(Collectors.toMap(k -> String.valueOf(k.getCatId()), v -> {
                    List<CategoryEntity> categoryEntityList = baseMapper.selectList(new LambdaQueryWrapper<CategoryEntity>()
                            .eq(CategoryEntity::getParentCid, v.getCatId()));

                    // 组装二级分类
                    List<Catalog2VO> catalog2VOList = new ArrayList<>();
                    if (categoryEntityList != null && !categoryEntityList.isEmpty()) {
                        catalog2VOList = categoryEntityList.stream().map(item -> {

                            // 组装三级分类
                            List<CategoryEntity> categoryEntities = baseMapper.selectList(new LambdaQueryWrapper<CategoryEntity>().eq(CategoryEntity::getParentCid, item.getCatId()));
                            List<Catalog2VO.Catalog3Vo> catalog3VoList = new ArrayList<>();
                            if (categoryEntities != null && !categoryEntities.isEmpty()) {
                                catalog3VoList = categoryEntities.stream()
                                        .map(level3 -> new Catalog2VO.Catalog3Vo(item.getCatId().toString(), level3.getCatId().toString(), level3.getName())).collect(Collectors.toList());
                            }
                            return new Catalog2VO(v.getCatId().toString(), catalog3VoList, item.getCatId().toString(), item.getName());
                        }).collect(Collectors.toList());
                    }

                    return catalog2VOList;
                }));

        // 5.结果集存入redis
        // 关注锁时序问题，存入redis代码块必须在当前锁中执行
        stringRedisTemplate.opsForValue().set("categoryJson", JSONObject.toJSONString(result), 1, TimeUnit.DAYS);
        return result;
    }

    @Override
    public Map<String, List<Catalog2VO>> getCatalogJson() {
        return Collections.emptyMap();
    }

    private List<Long> getParentPath(Long catelogId, List<Long> paths) {
        paths.add(catelogId);
        CategoryEntity category = baseMapper.selectById(catelogId);
        if(category.getParentCid() != 0){
            getParentPath(category.getParentCid(), paths);
        }
        return paths;
    }

    private List<CategoryEntity> getCurrentMenuChildren(CategoryEntity categoryEntity, List<CategoryEntity> categoryEntityList) {

        return categoryEntityList.stream()
                .filter(item -> item.getParentCid().equals(categoryEntity.getCatId()))
                // 递归设置当前菜单的子菜单
                .peek(item -> item.setChildren(getCurrentMenuChildren(item, categoryEntityList)))
                // 排序
                .sorted(Comparator.comparingInt(CategoryEntity::getSort))
                .collect(Collectors.toList());
    }

}
